/**
 *  Serponix is an arcade game in focus to multiplayer based on the classic game Snake.
 *  Copyright (C) 2010 - 2011  Daniel Vala
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License,
 *  or  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  If you have any question do not hesitate to contact author
 *  on e-mail address: danielvala42@gmail.com
 */
package com.serponix.game;

import com.serponix.game.objektyNaPlose.VecNaPolicku;
import com.serponix.game.objektyNaPlose.Clanek;
import com.serponix.game.food.Cherry;
import com.serponix.game.food.Cake;
import com.serponix.game.objektyNaPlose.ViceObjektu;
import com.serponix.game.objektyNaPlose.Zed;
import com.serponix.game.score.Score;
import com.serponix.game.projectiles.Laser;
import com.serponix.game.projectiles.Rocket;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import com.serponix.net.Informace;

/**
 * Had obsahuje informace o poloze všech jeho článků, směru a vlastnostech.
 * @author Daniel Vala
 */
public final class Snake {

    private Player player;
    private List<Clanek> clanky;
    private int startX, startY, tloustka;
    private Color color;
    /**
     * Actual direction of the snake (where snake will go his next move).
     */
    private Direction direction;
    /**
     * if snake do not move, had some direction before stoping (other then NOWHERE).
     */
    private Direction directionBeforeStop;
    private GameModel model;
    public Score score;
    private List<Boolean> zasobniky;
    private boolean rocketLoaded;
    private int pocetRaket;
    private int startVitality;
    private int vitality;
    private boolean stopOneRound;
    private boolean strelbySchopny;
    private boolean jedoveStrely, odolnost, tank, schopnyZastavit, obraceneKlavesy, svobodny, duch, tron, neviditelnost, pstros, slowdown;
    // odpočítáveče
    private int tankCounter, jedoveStrelyCounter, obraceneKlavesyCounter, odolnostCounter, duchCounter, neviditelnostCounter, zpomalenyCounter;
    private String tronInfo = "";
    private boolean blikajici, blikajiciTemp;
    private String pathTextura;
    private int pocatecniDelka;
    private int minimalniVelikost = 5;
    private int velikostProVitezstvi, velikostProVitezstviTemp;
    // mrtvý had nemá žádné vlastnosti a je v počáteční velikosti
    private boolean zije;

    /**
     * Kopírovací kontruktor vytvoří hada podle hada předaného v parametru konstruktoru.
     * Vytvoří ho se stejnými parametry, jako měl předaný had při vytvoření a zkopíruje jeho aktuální články.
     * @param had Had, podle kterého se bude vytvářet had nový.
     */
    public Snake(Snake had) {
        this(had.model, had.player, had.startX, had.startY, had.direction,
                had.startVitality, had.tloustka, had.pocatecniDelka, had.velikostProVitezstvi,
                had.getBarva(), had.pathTextura, had.strelbySchopny, had.blikajici);
        clanky = had.clanky;
    }

    /**
     * Vytvoří nového hada.
     * @param model Model hry, ve kterém bude had existovat.
     * @param jmeno Jméno hada (hráče hry).
     * @param startX Počáteční pozice hada x.
     * @param startY Počáteční pozice hada y.
     * @param direction Počáteční směr hada.
     * @param startVitality Počáteční růst hada.
     * @param tloustka Definuje, jak bude had široký.
     * @param pocatecniDelka Počáteční počet článků hada.
     * @param velikostProVitezstvi Pokud had dosáhne dané velikosti, zvítězí. Pro neomezenou velikost nastavte 0.
     * @param barva Barva hada.
     * @param pathTextura Textura hada.
     * @param strelbySchopny Definuje, zda bude had schopen střelby.
     * @param blikajici Definuje, zda bude hadovi při velikosti blízké k vítězství blikat ocas.
     */
    public Snake(GameModel model, Player hrac, int startX, int startY, Direction direction,
            int startVitality, int tloustka, int pocatecniDelka, int velikostProVitezstvi,
            Color barva, String pathTextura, boolean strelbySchopny, boolean blikajici) {
        zije = true;
        this.player = hrac;
        this.model = model;
        this.startX = startX;
        this.startY = startY;
        this.direction = direction;
        this.startVitality = startVitality;
        this.tloustka = tloustka;
        this.pocatecniDelka = pocatecniDelka;
        this.velikostProVitezstvi = velikostProVitezstvi;
        this.color = barva;
        this.pathTextura = pathTextura;
        this.strelbySchopny = strelbySchopny;
        this.blikajici = blikajici;
        score = new Score(this, model);

        // vytvoření LinkedListu a vložení nových článků podle startovní pozice a počáteční velikosti hada
        clanky = new LinkedList<Clanek>();
        for (int i = 0; i < pocatecniDelka; i++) {
            clanky.add(new Clanek(this, startX, startY));
        }
        resetAbilities();
        rocketLoaded = true;
    }

    @Override
    public String toString() {
        return getJmeno();
    }

    /**
     * Vrátí jméno hráče, který ovládá tohoto hada.
     * @return jméno hráče, který ovládá tohoto hada.
     */
    public String getJmeno() {
        return player.getJmeno();
    }

    /**
     * Zjistí, jakému hráči had patří.
     * @return Hráč, kterému had patří.
     */
    public Player getHrac() {
        return player;
    }

    /**
     * Zjistí x souřadnici hlavy hada.
     * @return X souřadnice hlavy.
     */
    public int getX() {
        return clanky.get(0).getX();
    }

    /**
     * Zjistí y souřadnici hlavy hada.
     * @return Y souřadnice hlavy.
     */
    public int getY() {
        return clanky.get(0).getY();
    }

    public int getXProVzhled() {
        return getX() * tloustka;
    }

    public int getYProVzhled() {
        return getY() * tloustka;
    }

    public void odstranClanek(int cisloClanku) {
        clanky.get(cisloClanku).odmapuj(model);
        clanky.remove(cisloClanku);
    }

    /**
     * Zjistí aktuální délku hada.
     * @return Aktuální délka (počet článků hada).
     */
    public int getDelka() {
        return clanky.size();
    }

    /**
     * Nastaví počet článků hada podle zadané hodnoty.
     * @param delka Nová velikost hada.
     */
    private void setDelkaHada(int delka) {
        if (getDelka() > delka) {
            usekniOcas(delka - 1);
        } else if (getDelka() < delka) {
            int narust = delka - getDelka();
            prodluzHada(narust);
        }
    }

    /**
     * Had naroste o počet článků podle jeho proměnné nárust.
     * Had musí mít alespoň jeden článek.
     */
    public void prodluzHada() {
        prodluzHada(vitality);
    }

    /**
     * Had naroste o zadaný počet článků.
     * Nelze použít záporná čísla. Pro zkrácení použijte metodu zkratHada.
     * Had musí mít alespoň jeden článek.
     * Nový článek se umístí na stejné místo, jako je poslední článek.
     * Na tomto místě tedy budou v tuto chvíli 2 nebo více článků.
     * @param narust Počet článků, o které má had narůst.
     */
    public void prodluzHada(int narust) {
        for (int i = 0; i < narust; i++) {
            Clanek posledniClanek = clanky.get(getDelka() - 1);
            clanky.add(new Clanek(this, posledniClanek.getX(), posledniClanek.getY()));
        }
        if (velikostProVitezstvi != 0 && getDelka() >= velikostProVitezstvi) {
            score.vyhra();
        }
    }

    /**
     * Zkrátí hada o daný pocet kostiček. Vždy však zůstane ve své minimální velikosti.
     * Nelze použít záporná čísla. Pro prodloužení hada použijte metodu narust.
     * @param pocetKosticek Počet kostiček, o který se had zkrátí.
     * @return true, pokud had důsledkem zkrácení zemřel. Jinak false.
     */
    public boolean zkratHada(int pocetClanku) {
        // Pokud se má had zkrátit o tolik článků, že už by byl menší než jeho minimální velikost, zemře.
        if (getDelka() - minimalniVelikost < pocetClanku) {
            smrt();
            return true;
        } else {
            for (int i = 0; i < pocetClanku; i++) {
                odstranClanek(getDelka() - 1);
            }
            return false;
        }
    }

    /**
     * Zjistí, zda je had naživu.
     * @return true, pokud žije. False, pokud je mrtev.
     */
    public boolean isAlive() {
        return zije;
    }

    /**
     * Zjistí barvu hada.
     * Ne na všech článcích ale musí mít had stejnou barvu.
     * Pro zjištění barvy hada na daném článku použijte getBarva(int clanekHada)
     * @return Barva hada.
     * @see getBarva(int clanekHada)
     */
    public Color getBarva() {
        return color;
    }

    /**
     * Zjistí barvu hada v jeho daném článku.
     * Může být jiná, než při volání metody getBarva(), protože některé články mohou mít jinou barvu, než obecně had má.
     * @param clanekHada Článek hada od 0 do délky hada - 1.
     * @return Barva hada. Null pokud daný článek hada neexistuje.
     * @see getBarva()
     */
    public Color getBarva(int clanekHada) {
        if (clanekHada > getDelka() - 1) {
            return null;
        }
        if (blikajici && clanekHada > velikostProVitezstvi - 12) {
            return clanekHada % 2 == 0 ? Color.RED : Color.BLUE;
        }
        return getBarva();
    }

    /**
     * Set all abilities to a default state.
     */
    public void resetAbilities() {
        setPocetZasobniku(1);
        pocetRaket = 0;
        vitality = startVitality;
        setVlastnostDuch(false);
        setVlastnostJedoveStrely(false);
        setVlastnostZastaveni(false);
        setVlastnostSvoboda(false);
        setVlastnostObraceneKlavesy(false);
        setVlastnostPstros(false);
        setVlastnostOdolnost(false);
        setVlastnostTank(false);
        setVlastnostNeviditelnost(false);
        setVlastnostZpomaleni(false);
    }

    /**
     * Pokud je had živý, umře. Ztratí schopnost pohybovat se a střílet.
     * Pokud po smrti hada zůstane ve hře jen jediný živý had,
     * stane se poslední živý had vítězem.
     * @return <tt> true </tt> pokud po smrti hada skončilo kolo.
     */
    public boolean smrt() {
        if (zije) {
            zije = false;
            model.getPanelZprav().addMessage(String.format("%s byl zabit!", getJmeno()));
            return model.isWinner();
        }
        return false;
    }

    /**
     * Resetuje hada (nastaví do původních hodnot) a oživne ho.
     * Vymaže skóre.
     */
    public void oziv() {
        setDelkaHada(pocatecniDelka);
        resetAbilities();
        score.reset();
        rocketLoaded = true; // TODO melo by byt spise v resetVlastnosti
        zije = true;
    }

    /**
     * Přesune hada na zadané souřadnice a nastaví mu zadaný směr.
     * @param x Nová souřadnice hlavy hada.
     * @param y Nová souřadnice hlavy hada.
     * @param smer Nový směr hada.
     */
    public void setHad(int x, int y, Direction smer) {
        odmapujVsechnyClanky();
        for (int i = 0; i < clanky.size(); i++) {
            clanky.get(i).move(x, y);
        }
        this.direction = smer;
    }

    /**
     * Vrátí počet článků, který had musí mít, aby zvítězil.
     * @return Počet článků hada k nutných k vítězství.
     */
    public int getVelikostProVitezstvi() {
        return velikostProVitezstvi;
    }

    /**
     * Vrátí aktuální směr hada.
     * @return Aktuální směr.
     */
    public Direction getSmer() {
        return direction;
    }

    /**
     * Vrátí směr hada, přicemž nikdy nevrátí směr NIKAM.
     * Pokud tedy had aktuálně stojí, vrátí se směr před jeho zastavením.
     * @return Aktuální směr nebo směr před zastavením hada.
     */
    public Direction getExistujiciSmer() {
        if (direction != Direction.NOWHERE) {
            return direction;
        } else {
            return directionBeforeStop;
        }
    }

    /**
     * Nastaví hadovi zadaný směr, ať je jakýkoliv.
     * Nebere v potaz žádné stavy hada.
     * @param smer Nový směr hada.
     */
    public void setSmer(Direction smer) {
        this.direction = smer;
    }

    /**
     * Změní směr hada, přičemž nedovoluje změnit směr na opačný, než je právě nastaven.
     * Pokud má ale had schopnost zastavit, tak při nastavení opačného směru had zastaví.
     * Jestliže je had mrtev, není schopen měnit směr.
     * Pokud má had obrácené klávesy, nastaví směr opačně, než je zadáno.
     * Pro potřeby změnit směr hada natvrdo lze použít veřejnou proměnou smer.
     * @param smer Nový směr hada.
     */
    public void changeSmer(Direction smer) {
        // pokud had nežije, nemá možnost měnit svůj směr
        if (!zije) {
            return;
        }

        if (obraceneKlavesy) {
            smer = Direction.obratSmer(smer);
        }

        // pokud je směr opačný než aktuální, nebo při směru NIKAM se had pokusí zastavit
        if (Direction.obratSmer(smer) == this.direction || smer == Direction.NOWHERE) {
            zastav();
            return;
        }

        // pokud had stojí, nelze změnit směr na směr opačný, který měl had před zastavením,
        // protože by naboural sám do sebe
        if (this.direction == Direction.NOWHERE && Direction.obratSmer(smer) == directionBeforeStop) {
            return;
        }
        this.direction = smer; // nastavení směru za normálních podmínek
    }

    /**
     * Pokud má had schopnost zastavení, zastaví.
     * @return true, pokud had zastavil, jinak false.
     */
    private boolean zastav() {
        if (schopnyZastavit) {
            if (direction != Direction.NOWHERE) {
                directionBeforeStop = direction;
            }
            direction = Direction.NOWHERE;
            return true;
        }
        return false;
    }

    /**
     * Vrátí číslo článku, který leží na zadaných souřadnicích. <br />
     * 0 = hlava <br />
     * 1 = jeden článek za hlavou atd. <br />
     * @param x Souřadnice x článku.
     * @param y Souřadnice y článku.
     * @return Číslo článku hada, který leží na zadaných souřadnicích nebo
     * -1 pokud na zadaných souřadnicích žádný článek není.
     */
    public int getClanek(int x, int y) {
        for (int i = 0; i < clanky.size(); i++) {
            if (clanky.get(i).getX() == x && clanky.get(i).getY() == y) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Zasáhne článek hada, který se nachází v daných souřadnicích.
     * @param laser Laser, který zasáhne daný článek.
     * @param x Souřadnice zasaženého článku x.
     * @param y Souřadnice zasaženého článku y.
     * @return true, pokud byl zásah úspěšný. Jinak false.
     */
    public boolean zasah(Laser laser, int x, int y) {
        int cisloClanku = getClanek(x, y);
        if (cisloClanku != -1) {
            return zasah(laser, cisloClanku);
        } else {
            return false;
        }
    }

    /**
     * Had v daném článku obdrží zásah laserem a článek zmizí.
     * Metoda bere v úvahu vlastnost odolnost, při kterém článek hada
     * zůstane zachován a střela se odrazí
     * a vlastnost duch, při kterém střela projde skrz.
     * Pokud je střela smrtící, resetuje hada.
     * Pokud je zasažena hlava hada, had se resetuje.
     * @param laser Laser, který zasáhl hada.
     * @param cisloClanku Článek, který laser zasáhl.
     * @return true, pokud hada zásah poškodil, jinak false.
     */
    public boolean zasah(Laser laser, int cisloClanku) {
        if (duch) {
            return false;
        }
//        if (tron) {
//            if (cisloClanku > 0 && cisloClanku < getDelka()) {
//                clanky.remove(cisloClanku);
//                laser.znicSe();
//            }
//            tronInfo = "PAL " + cisloClanku;
//            return true;
//        }
        // pokud byl had zasažen do hlavy, resetuje se
        if (cisloClanku == 0) {
            if (getDelka() > 1) {
                odstranClanek(0);
            }
            model.resetHada(this);
            if (this.getJmeno().equals(laser.getJmenoVlastnika())) {
                model.getPanelZprav().addMessage(String.format("%s dostal headshot sám od sebe!", this.getJmeno()));
            } else {
                model.getPanelZprav().addMessage(String.format("%s dostal headshot od hráče %s", this.getJmeno(), laser.getJmenoVlastnika()));
            }
            return true;
        }
        // jestliže má had odolnost a nebyl zasažen do hlavy,
        // střela se od něj pouze odrazí
        if (odolnost) {
            laser.odrazSe();
            return false;
        }
        // jestliže daný článek hada existuje, odstraní se a střela zanikne
        if (cisloClanku > 0 && cisloClanku < getDelka()) {
            odstranClanek(cisloClanku);
        }
        // jestliže byla střela smrtící, had se přesune na startovní pozici
        if (laser.isSmrtici()) {
            model.resetHada(this);
        }
        // zkontrolování velikosti hada a případná smrt
        checkSmrt();
        return true;
    }

    /**
     * Usekne ocas v článku podle zadaného indexu.
     * @param index Index článku, kde se má had useknout.
     */
    public void usekniOcas(int index) {
        if (index >= 0 && index < getDelka()) {
            while (index < clanky.size() - 1) {
                odstranClanek(index);
            }
        }
    }

    public void usekniOcas(int x, int y) {
        usekniOcas(getClanek(x, y));
    }

    /**
     * Usekne ocas v článku podle zadaného indexu tak, že z ocasu zbyde polovina. Každý druhý článek useklého ocasu zůstane.
     * Pokud je had duchem, nic se nestane.
     * @param index Index článku, kde se má had useknout.
     */
    public void usekniEfektneOcas(int index) {
        if (duch) {
            return;
        }
        if (index >= 0 && index < getDelka()) {
            if (tron) {
                usekniOcas(index);
            }
            for (int i = index; i < clanky.size() - 1; i++) {
                odstranClanek(i);
            }
            checkSmrt();
        }
    }

    public void usekniEfektneOcas(int x, int y) {
        usekniEfektneOcas(getClanek(x, y));
    }

    /**
     * Zkontroluje, zda má had stale potřebnou délku k přežití.
     * V opačném případě ho zabije.
     * @return <tt> true </tt> pokud had zemřel.
     */
    private boolean checkSmrt() {
        if (getDelka() < minimalniVelikost) {
            smrt();
            return true;
        }
        return false;
    }

    /**
     * Pokud je had naživu a v současné chvíli schopný střelby, vystřelí laser.
     * @return <tt>true</tt> pokud vystřelil.
     */
    public boolean strelLaser() {
        if (isAlive() && strelbySchopny) {
            for (int i = 0; i < getPocetZasobniku(); i++) { // pro tento počet zásobníků
                if (zasobniky.get(i) == true) { // jestliže je v daném zásobníku připravená střela, had vystřelí
                    model.addStrela(new Laser(model, this, getX(), getY(), getExistujiciSmer(), 3, i, isVlastnostJedoveStrely()));
                    zasobniky.set(i, false); // střela už není v zásobníku, nelze ji tedy z něj vystřelit
                    return true; // po vystřelení už není potřeba procházet další zásobníky
                }
            }
        }
        return false;
    }

    /**
     * Pokud had žije, vlastní raketu a je schopný ji vystřelit, vystřelí ji.
     * @return <tt>true</tt> pokud vystřelil.
     */
    public boolean strelRaketu() {
        if (isAlive() && strelbySchopny && pocetRaket > 0) {
            if (rocketLoaded) {
                model.addStrela(new Rocket(model, this, getX(), getY(), getExistujiciSmer(), 2));
                rocketLoaded = false;
                pocetRaket--;
                return true;
            }
        }
        return false;
    }

    /**
     * Nabije daný zásobník laserem. Bude tedy možné znovu z něj vystřelit.
     * @param poziceVZasobniku Pozice v zásobníku od 0 do počtu počtu zásobníků - 1.
     */
    public void nabijZasobnik(int poziceVZasobniku) {
        // zkontroluje, zda pozice zásobníku existuje
        if (poziceVZasobniku >= 0 && poziceVZasobniku < getPocetZasobniku()) {
            zasobniky.set(poziceVZasobniku, true);
        } else {
            throw new RuntimeException("Nabíjení neexistujícího zásobníku. Číslo zásobníku: " + poziceVZasobniku);
            // TODO vyhodit vlastni vyjimku pokus o nabijeni neexistujiciho zasobniku a logovat
        }
    }

    /**
     * Nabije novou raketu. Pokud had tedy má ještě vice než 0 raket, bude moci jednu vystřelit.
     */
    public void nabijRaketu() {
        rocketLoaded = true;
    }

    ////////////////////////////////////////////////////////////////////////
    /////////////////////////  VLASTNOSTI HADA /////////////////////////////
    ////////////////////////////////////////////////////////////////////////
    //                                                                    //
    ///////////////////////////////   IS   /////////////////////////////////
    /**
     * Zjistí, jestli je had duch.
     * @return <tt>true</tt> pokud je had duch.
     */
    public boolean isVlastnostDuch() {
        return duch;
    }

    /**
     * Zjistí, zda má had střely napuštěné jedem.
     * @return <tt>true</tt> pokud had má střely napuštené jedem.
     */
    public boolean isVlastnostJedoveStrely() {
        return jedoveStrely;
    }

    /**
     * Zjistí, zda je had schopný zastavit.
     * @return <tt>true</tt>, pokud je had schopný zastavit.
     */
    public boolean isVlastnostZastaveni() {
        return schopnyZastavit;
    }

    /**
     * Zjistí, zda je had svobodný.
     * @return <tt>true</tt> pokud je had svobodný.
     */
    public boolean isVlastnostSvoboda() {
        return svobodny;
    }

    /**
     * Zjistí, zda had má obrácené klávesy.
     * @return <tt>true</tt> pokud had má obrácené klávesy.
     */
    public boolean isVlastnostObraceneKlavesy() {
        return obraceneKlavesy;
    }

    /**
     * Zjistí, zda je had pštrosem.
     * @return <tt>true</tt> pokud had má vlastnost pštros.
     */
    public boolean isVlastnostPstros() {
        return pstros;
    }

    /**
     * Zjistí, zda je had odolný vůči střelám.
     * @return <tt>true</tt> pokud had má vlastnost odolnost.
     */
    public boolean isVlastnostOdolny() {
        return odolnost;
    }

    /**
     * Zjistí, zda má had vlastnost tank.
     * @return <tt>true</tt> pokud had má vlastnost tank.
     */
    public boolean isVlastnostTank() {
        return tank;
    }

    /**
     * Zjistí, zda je had neviditelný.
     * @return <tt>true</tt> pokud je had neviditelný.
     */
    public boolean isVlastnostNeviditelnost() {
        return neviditelnost;
    }

    /**
     * Zjisti, zda je had zpomalený. Tzn. pohybuje se dvakrát pomaleji, než normálně.
     * @return <tt>true</tt> pokud je had zpomalený.
     */
    public boolean isVlastnostZpomaleny() {
        return slowdown;
    }

    ///////////////////////////// GET ///////////////////////////////////////
    /**
     * Zjistí, kolik had může mít v jednu chvíli vystřelených laserů.
     * @return Počet zásobníků.
     */
    public int getPocetZasobniku() {
        return zasobniky.size();
    }

    /**
     * Zjistí, kolik má had aktuálně raket.
     * @return Počet raket, který had vlastní.
     */
    public int getPocetRaket() {
        return pocetRaket;
    }

    /**
     * Zjistí, jakou má had vitalitu a o kolik tedy roste při každém sežrání jídla.
     * @return Počet článků, které hadovi narostou při sežrání jídla.
     */
    public int getVitalita() {
        return vitality;
    }

    ///////////////////////////// ADD ///////////////////////////////////////
    /**
     * Přidá hadovi daný počet zásobníků na laser.
     * @param pocet Počet zásobníků, který se přidá.
     */
    public void addZasobniky(int pocet) {
        for (int i = 0; i < pocet; i++) {
            zasobniky.add(true);
        }
    }

    /**
     * Přidá hadovi daný počet raket.
     * @param pocet Počet raket, který had získá.
     */
    public void addRakety(int pocet) {
        pocetRaket += pocet;
    }

    /**
     * Zrychlí růst hada při každém sebrání jídla o daný počet článků.
     * @param vitalita Počet článků, o který bude had růst.
     */
    public void addVitalita(int vitalita) {
        this.vitality += vitalita;
    }

    ///////////////////////////// SET ///////////////////////////////////////
    /**
     * Vytvoří zadaný počet nových zásobníků laseru. Staré smaže.
     * Všechny zásobníky budou mít nabitý laser.
     * @param pocet Počet zásobníků, který bude had vlastnit.
     */
    private void setPocetZasobniku(int pocet) {
        zasobniky = new LinkedList<Boolean>(); // vytvoření zásobníku pro střely
        addZasobniky(pocet);
    }

    /**
     * Had se stane duchem na 600 kroků.
     * Projde skrz všechny objekty jako jsou hadi a střely.
     * Při požadavku na nastavení ducha na true je také zrušena vlastnost tank (pokud ji had měl).
     * @param duch True, pokud had má být duchem, jinak false.
     * @return True, pokud se vlastnost změnila. Jinak false.
     */
    public boolean setVlastnostDuch(boolean duch) {
        if (duch) {
            tank = false;
        }
        if (duch) {
            duchCounter += 600;
        } else {
            duchCounter = 0;
        }
        boolean returnValue = this.duch != duch;
        this.duch = duch;
        return returnValue;
    }

    /**
     * Had získá vlastnost smrtící střely na 1000 kroků.
     * Kazdý vystřelený laser zasaženého hada včetně zničení jednoho článku resetuje.
     * @param jedoveStrely True, pokud had má mít jedové střely, jinak false.
     * @return True, pokud se vlastnost změnila. Jinak false.
     */
    public boolean setVlastnostJedoveStrely(boolean jedoveStrely) {
        if (jedoveStrely) {
            jedoveStrelyCounter += 1000;
        } else {
            jedoveStrelyCounter = 0;
        }
        boolean returnValue = this.jedoveStrely != jedoveStrely;
        this.jedoveStrely = jedoveStrely;
        return returnValue;
    }

    /**
     * Had získá schopnost zastavit.
     * Směrovou klávesou opačného směru, než v jakém směru se had pohybuje, had zastaví.
     * @param schopnostZastaveni True, pokud had má mít schopnost zastavení, jinak false.
     * @return True, pokud se vlastnost změnila. Jinak false.
     */
    public boolean setVlastnostZastaveni(boolean schopnostZastaveni) {
        boolean returnValue = this.schopnyZastavit != schopnostZastaveni;
        this.schopnyZastavit = schopnostZastaveni;
        return returnValue;
    }

    /**
     * Pokud je had svobodný a "narazí" do okraje herní plochy,
     * objeví se zrcadlově na druhé straně.
     * Při požadavku na nastavení svobody na true je také zrušena vlastnost pštros (pokud ji had měl).
     * @param svoboda True, pokud had má mít svobodu. False, pokud ne.
     * @return True, pokud byla vlastnost změněna. Jinak false.
     */
    public boolean setVlastnostSvoboda(boolean svoboda) {
        if (svoboda) {
            pstros = false;
        }
        boolean returnValue = this.svobodny != svoboda;
        this.svobodny = svoboda;
        return returnValue;
    }

    /**
     * Hadovi se obrátí směrové klávesy. Doprava bude doleva, nahoru bude dolu a naopak.
     * Tato vlastnost je dočasná a to na 800 kroků.
     * @param obraceneKlavesy True, pokud had má mít obrácené klávesy. False, pokud ne.
     * @return True, pokud byla vlastnost změněna. Jinak false.
     */
    public boolean setVlastnostObraceneKlavesy(boolean obraceneKlavesy) {
        if (obraceneKlavesy) {
            obraceneKlavesyCounter += 700;
        } else {
            obraceneKlavesyCounter = 0;
        }
        boolean returnValue = this.obraceneKlavesy != obraceneKlavesy;
        this.obraceneKlavesy = obraceneKlavesy;
        return returnValue;
    }

    /**
     * Had s vlastností pštros nemůže narazit do krajní zdi.
     * Může se pohybovat i mimo herní plochu.
     * @param pstros True, pokud má mít had vlastnost pštros. False, pokud ne.
     * @return True, pokud byla vlastnost změněna. Jinak false.
     */
    public boolean setVlastnostPstros(boolean pstros) {
        boolean returnValue = this.pstros != pstros;
        this.pstros = pstros;
        return returnValue;
    }

    /**
     * Had získá vlastnost odolnost na 1000 kroků.
     * Všechny lasery se od hada odrazí.
     * Při požadavku na nastavení odolnosti na true je také zrušena vlastnost duch (pokud ji had měl).
     */
    public boolean setVlastnostOdolnost(boolean odolnost) {
        if (odolnost) {
            duch = false;
        }
        if (odolnost) {
            odolnostCounter += 1000;
        } else {
            odolnostCounter = 0;
        }
        boolean returnValue = this.odolnost != odolnost;
        this.odolnost = odolnost;
        return returnValue;
    }

    /**
     * Had získá na 1000 kroků vlastnost tank, která mu umožnuje prorážet hlavou ostatní hady, které tak resetuje.
     * Při požadavku na nastavení odolnosti na true je také zrušena vlastnost duch (pokud ji had měl).
     * @param tank True, pokud má mít had vlastnost tank. False, pokud ne.
     * @return True, pokud byla vlastnost změněna. Jinak false.
     */
    public boolean setVlastnostTank(boolean tank) {
        if (tank) {
            duch = false;
        }
        if (tank) {
            tankCounter += 1000;
        } else {
            tankCounter = 0;
        }
        boolean returnValue = this.tank != tank;
        this.tank = tank;
        return returnValue;
    }

    /**
     * Had se stane neviditelným.
     */
    /**
     * Had se stane neviditelným pro všechny ostatní hady (pokud protihráč nehraje na stejném pc).
     * @param neviditelnost True, pokud má mít had vlastnost neviditelnost. False, pokud ne.
     * @return True, pokud byla vlastnost změněna. Jinak false.
     */
    public boolean setVlastnostNeviditelnost(boolean neviditelnost) {
        if (neviditelnost) {
            neviditelnostCounter += 1000;
        } else {
            neviditelnostCounter = 0;
        }
        boolean returnValue = this.neviditelnost != neviditelnost;
        this.neviditelnost = neviditelnost;
        return returnValue;
    }

    /**
     * Had se bude pohybovat jen jednou za 2 kola.
     * @param zpomaleny True, pokud má být had zpomalený. False, pokud ne.
     * @return True, pokud byla vlastnost změněna. Jinak false.
     */
    public boolean setVlastnostZpomaleni(boolean zpomaleny) {
        if (zpomaleny) {
            zpomalenyCounter += 1000;
        } else {
            zpomalenyCounter = 0;
        }
        boolean returnValue = this.slowdown != zpomaleny;
        this.slowdown = zpomaleny;
        return returnValue;
    }

    ////////////////////////////////////////////////////////////////////////
    //////////////////////  KONEC VLASTNOSTÍ HADA //////////////////////////
    ////////////////////////////////////////////////////////////////////////
    /**
     * Had zdvojnásobí svojí velikost.
     */
    public void zdvojnasobSe() {
        prodluzHada(getDelka());
        if (velikostProVitezstvi != 0 && getDelka() >= velikostProVitezstvi) {
            score.vyhra();
        }
    }

    public boolean ziskejVlastnostTron() {
        if (tron) {
            return true;
        }
        velikostProVitezstviTemp = velikostProVitezstvi;
        velikostProVitezstvi = 0;
        blikajici = false;
        tron = true;
        tronInfo = "VSE";
        return false;
    }

    public void ztratVlastnostTron() {
        if (tron) {
            velikostProVitezstvi = velikostProVitezstviTemp;
            blikajici = blikajiciTemp;
            tron = false;
            tronInfo = "SMAZ";
        }
    }

    public boolean isTron() {
        return tron;
    }

    /**
     * Prohodí hada s předaným hadem. Vymění si všechno včetně velikosti, pozice a směru.
     * @param had Had, s kterým se tento had prohodí.
     */
    public void swapSnakes(Snake had) {
        odmapujVsechnyClanky();
        had.odmapujVsechnyClanky();

        // uložení parametrů článků hada a jeho směru do temp proměnných
        List<Dimension> tempSouradniceClanku = getDimensionFromClanky(clanky);
        Direction tempSmer = direction;

        // nastavení tohoto hada podle zadaného
        swapAllClanky(had, this);
        direction = had.getSmer();

        // nastavení zadaného hada podle tohoto, uloženém v dočasném
        swapAllClanky(tempSouradniceClanku, had);
        had.direction = tempSmer;

        namapujVsechnyClanky();
        had.namapujVsechnyClanky();
    }

    /**
     * Vrátí seznam souřadnic předaných článků.
     * @param clanky Články, z kterých chceme zjistit souřadnice.
     * @return Seznam souřadnic článků v poli Dimension.
     */
    private static List<Dimension> getDimensionFromClanky(List<Clanek> clanky) {
        List<Dimension> souradniceClanku = new ArrayList<Dimension>();
        for (Clanek clanek : clanky) {
            souradniceClanku.add(new Dimension(clanek.getX(), clanek.getY()));
        }
        return souradniceClanku;
    }

    /**
     * Kopírování článků podle zadaných souřadnic do hada.
     * @param souradniceClanku Souřadnice, které budou použity pro tvorbu nových článků hada.
     * @param had Had, který dostane nové články podle zadaných souřadnic.
     */
    private static void swapAllClanky(List<Dimension> souradniceClanku, Snake had) {
        List<Clanek> noveClanky = new ArrayList<Clanek>();
        for (Dimension dimension : souradniceClanku) {
            noveClanky.add(new Clanek(had, (int) dimension.getWidth(), (int) dimension.getHeight()));
        }
        had.clanky = noveClanky;
    }

    /**
     * Kopírování článů z hada1 do hada2.
     * Had2 si vytvoří své nové články podle počtu a souřadnic hada1.
     * @param had1 Had, jehož články budou použity k vytvoření článků hadaě.
     * @param had2 Had, který bude prohozen s hadem1.
     */
    private static void swapAllClanky(Snake had1, Snake had2) {
        List<Clanek> noveClanky = new ArrayList<Clanek>();
        for (Clanek clanek : had1.clanky) {
            noveClanky.add(new Clanek(had2, clanek.getX(), clanek.getY()));
        }
        had2.clanky = noveClanky;
    }

    /**
     * Všechny countery sníží o 1.
     * Pokud dosáhne counter některé z vlastností 0, vlastnost se hadovi odebere.
     */
    private void counterAbilities() {
        if (tankCounter > 0) {
            tankCounter--;
            if (tankCounter == 0) {
                tank = false;
            }
        }
        if (jedoveStrelyCounter > 0) {
            jedoveStrelyCounter--;
            if (jedoveStrelyCounter == 0) {
                jedoveStrely = false;
            }
        }
        if (odolnostCounter > 0) {
            odolnostCounter--;
            if (odolnostCounter == 0) {
                odolnost = false;
            }
        }
        if (obraceneKlavesyCounter > 0) {
            obraceneKlavesyCounter--;
            if (obraceneKlavesyCounter == 0) {
                obraceneKlavesy = false;
            }
        }
        if (duchCounter > 0) {
            duchCounter--;
            if (duchCounter == 0) {
                duch = false;
            }
        }
        if (neviditelnostCounter > 0) {
            neviditelnostCounter--;
            if (neviditelnostCounter == 0) {
                neviditelnost = false;
            }
        }

        if (zpomalenyCounter > 0) {
            zpomalenyCounter--;
            if (zpomalenyCounter == 0) {
                slowdown = false;
            }
        }
    }

    public void namapujVsechnyClanky() {
        for (Clanek clanek : clanky) {
            clanek.namapuj(model);
        }
    }

    /**
     * Odstraní z globální mapy všechny články hada.
     */
    public void odmapujVsechnyClanky() {
        for (Clanek clanek : clanky) {
            clanek.odmapuj(model);
        }
    }

    /**
     * Vrátí objekt na herní ploše, který je umístěn před hadem ve směru jeho pohybu (nebo pohybu před zastavením).
     * @param vzdalenost Počet políček před hadem, kde chceme zjistit objekt na herní ploše.
     * @return Objekt na herní ploše nebo null, pokud zde žádný objekt není.
     */
    public VecNaPolicku getObjektPredHadem(int vzdalenost) {
        Point p = getPolickoPredHadem();
        return model.getObjektNaHerniPlose(p.x, p.y);
    }

    /**
     * Vrátí souřadnice políčka před hadem jako Point.
     * @return Políčko před hadem (i pokud had stojí).
     */
    private Point getPolickoPredHadem() {
        return model.getPolickoPred(getX(), getY(), getExistujiciSmer());
    }

    /**
     * Move snake one box in his direction and check collision.
     * @return Ability of snake to move. Does not matter, if actually moved.
     * In other words, return true if snake moved, or did not because of nowhere direction.
     * And return false if snake did not move because of slowdown.
     */
    public boolean move() {
        if (zije) {
            counterAbilities();

            if (stopOneRound) {
                stopOneRound = false;
                return false;
            }
            if (slowdown) {
                stopOneRound = true;
            }

            // posun hada (přesunutím zadního článku na místo posunu nové hlavy)
            if (direction != Direction.NOWHERE) {
                posunSe();
            }
        }
        return true;
    }

    /**
     * Zkontroluje objekty před hadem a zavolá kolize na tyto objekty.
     * Pokud lze pokračovat na volné místo před hadem, posune se tam.   
     */
    private void posunSe() {
        Point polickoPredHadem = getPolickoPredHadem();

        // Pokud had naboural, neposune se dál.
        if (kolize(polickoPredHadem.x, polickoPredHadem.y)) {
            return;
        }

        // posun zadního článku na místo nové hlavy
        // znovu se zjistí políčko před hadem, protože se mohlo po kolizi změnit
        posunZadnihoClankuDopredu();
    }

    /**
     * Posune zadní článek hada na volné místo před hadem.
     * Tento článek je pak hlavou hada, protože je nultý v LinkedListu.
     */
    private void posunZadnihoClankuDopredu() {
        // zjištění políčka před hadem
        Point polickoPredHadem = getPolickoPredHadem();

        // Zadek je poslední článek hada
        Clanek zadek = clanky.remove(clanky.size() - 1);

        // Při posunu se uvolní logické místo na jeho ploše.
        zadek.odmapuj(model);

        // A skutečně se přesune na místo nové hlavy.
        zadek.move(polickoPredHadem.x, polickoPredHadem.y);

        // Vrácení zpět do LinkedListu na 1.místo, kam hlava patří
        clanky.add(0, zadek);

        // Namapování logických informací. Na místě, kam se had posunul, je nová hlava!
        clanky.get(0).namapuj(model);
    }

    /**
     * Zkontroluje kolize s políčkem na pozici x a y, který by měl být před hadem.
     * Zkontroluje, zda je had v herní mapě. Pokud ne, zavolá metodu kolize s krajní stěnou.
     * Pokud je na tomto místě objekt, had se podle toho zachová.
     * @param x Pozice hlavy hada x.
     * @param y Pozice hlavy hada y.
     * @return true, pokud had narazil. False, pokud nenarazil.
     * False může vrátit i pokud na daném místě byl objekt, ale had vyhrál.
     * Např. sebral jídlo, nebo tankem resetoval jiného hada.
     */
    private boolean kolize(int x, int y) {
        if (checkBorderCollision(x, y)) {
            return true;
        }

        VecNaPolicku polickoPredHadem = model.getObjektNaHerniPlose(x, y);
        if (polickoPredHadem != null) {
            if (polickoPredHadem instanceof ViceObjektu) {
                ViceObjektu viceObjektu = (ViceObjektu) polickoPredHadem;
                for (VecNaPolicku vecNaPolicku : viceObjektu.getVeciNaPolicku()) {
                    if (kolizeSObjektem(vecNaPolicku)) {
                        return true;
                    }
                }
                return false;
            } else {
                return kolizeSObjektem(polickoPredHadem);
            }
        }
        return false;
    }

    private boolean kolizeSObjektem(VecNaPolicku objektPredHadem) {
        if (objektPredHadem instanceof Clanek) {
            return kolizeSClankem((Clanek) objektPredHadem);
        } else if (objektPredHadem instanceof Cake) {
            model.jidloSezrano(this);
            return false;
        } else if (objektPredHadem instanceof Cherry) {
            model.bonusSezran(this);
            return false;
        } else if (objektPredHadem instanceof Zed) {
            if (!duch) {
                model.resetHada(this);
                return true;
            } else {
                return false;
            }
            // pokud had hlavou narazí do laseru, zavolá se metoda na zásah hlavy laserem
        } else if (objektPredHadem instanceof Laser) {
            Laser laser = (Laser) objektPredHadem;
            boolean uspech = zasah(laser, 0);
            if (uspech) {
                laser.znicSe(); // TODO jestlize metodu zavola metoda kolize teto tridy, neodstrani se strela z modelu strel (tzn. ted to nefunguje, pokud had narazi hlavou do strely)
                return true;
            } else {
                return false; // pokud má had např. odolnost (i když v hlavě ji nikdy nemá)
            }
        } else if (objektPredHadem instanceof Rocket) {
            Rocket raketaPredHadem = (Rocket) objektPredHadem;
            usekniEfektneOcas(0);
            return true;
        }
        System.err.println("Kolize s neznámým objektem: " + objektPredHadem.getClass());
        return false;
    }

    /**
     * Zkontroluje, zda had nenarazil na stěny herní plochy a případně hada resetuje.
     * Pokud je had svobodný, místo nárazu do stěny herní plochy se objeví zrcadlově na druhé straně herní plochy.
     * Pštros se může pohybovat i mimo herní plochu.
     * @return true, pokud had narazil do stěny.
     * Vrátí true i v případě, kdy se teleportuje na druhou stranu a had se neresetuje.
     */
    public boolean checkBorderCollision(int x, int y) {
        // pokud je had uvnitř herní plochy, vrátí false
        if (model.isVHerniMape(x, y)) {
            return false;
        }


        if (pstros) {
            return false;
        }
        // Pokud je had svobodný, objeví se při překlenutí stěny herní plochy na druhé straně.
        if (svobodny) {
            if (x < 0) {
                // hlava se posune na místo mimo herní plochu a teprve poté se teleportuje na druhou stranu mapy
                posunZadnihoClankuDopredu();
                clanky.get(0).setX(model.getSirkaLogicka() - 1);
                return true;
            } else if (x > model.getSirkaLogicka() - 1) {
                posunZadnihoClankuDopredu();
                clanky.get(0).setX(0);
                return true;
            } else if (y < 0) {
                posunZadnihoClankuDopredu();
                clanky.get(0).setY(model.getVyskaLogicka() - 1);
                return true;
            } else if (y > model.getVyskaLogicka() - 1) {
                posunZadnihoClankuDopredu();
                clanky.get(0).setY(0);
                return true;
            }
            System.err.println("Chyba při kolizi s krajní stěnou.");
        }

        odmapujVsechnyClanky();
        model.resetHada(this);
        return true;
    }

    private boolean kolizeSClankem(Clanek clanek) {
        Snake nabouranyHad = clanek.getHad();
        if (duch || nabouranyHad.duch) {
            return false;
        }
        int cisloClanku = nabouranyHad.getClanek(clanek.getX(), clanek.getY());
        // jestliže narazil do hlavy jiného hada
        if (cisloClanku == 0) {
            return kolizeHlav(nabouranyHad);
        } // jestliže narazil do článku některého hada
        else {
            if (tank) {
                model.resetHada(nabouranyHad);
                boolean zemrel = zkratHada(1);
                if (zemrel || nabouranyHad.equals(this)) {
                    return true;
                }
                return false;
            } else {  // normální srážka hada
                model.resetHada(this);
                return true;
            }
        }
    }

    /**
     * Resetuje menšího hada. Při stejné velikosti resetuje oba.
     * Bere v úvahu vlastnost tank, která vždy přemůže hada bez tanku.
     * Pokud mají tank oba, opět se posuzují jejich velikosti.
     * @param had Had, který bude porovnávám s tímto hadem.
     */
    private boolean kolizeHlav(Snake had) {
        if (tank == false && had.tank == false) {
            return resetujMensihoHada(had);
        } else if (tank && had.tank == false) {
            model.resetHada(had);
            zkratHada(1);
            return false;
        } else if (tank == false && had.tank) {
            model.resetHada(this);
            had.zkratHada(1);
            return true;
        } else if (tank && had.tank) {
            return resetujMensihoHada(had);
        }
        return false;
    }

    /**
     * Resetuje menšího hada.
     * Při stejné velikosti resetuje oba hady.
     * @param had Had, jehož hlava se bude porovnávat s tímto hadem.
     */
    private boolean resetujMensihoHada(Snake had) {
        // pokud je větší had ten druhý, umírám já
        if (getDelka() < had.getDelka()) {
            model.resetHada(this);
            //   model.getPanelZprav().addZprava(String.format("%s a %s se srazili hlavama. Vyhrál %s", this.jmeno, jmeno, jmeno));
            return true;
        } // pokud jsem větší já, umírá druhý had
        else if (getDelka() > had.getDelka()) {
            model.resetHada(had);
            //   model.getPanelZprav().addZprava(String.format("%s a %s se srazili hlavama. Vyhrál %s", this.jmeno, jmeno, this.jmeno));
            return false;
            // pokud jsme stejně velcí, umíráme oba
        } else {
            model.resetHada(this);
            model.resetHada(had);
            //    model.getPanelZprav().addZprava(String.format("%s a %s se srazili hlavama. Umřeli oba.", this.jmeno, jmeno));
            return true;
        }
    }

    /**
     * Vrátí informace pro poslání o daném článku hada.
     * @param clanekHada Článek hada od 0 do délky hada - 1.
     * @return souřadnice x + Informace.ODDELOVAC3 + souřadnice y + Informace.ODDELOVAC3 + barva článku + Informace.ODDELOVAC3 + dodatečná informace + Informace.ODDELOVAC3 + směr článku[hlavy]
     * Null vrátí, pokud daný článek hada neexistuje.
     */
    private String getClanek(int clanekHada) {
        if (clanekHada > getDelka() - 1) {
            return null;
        }

        if (duch) {
            return clanky.get(clanekHada).getX() + Informace.ODDELOVAC3 + clanky.get(clanekHada).getY() + Informace.ODDELOVAC3 + getBarva(clanekHada).getRGB() + Informace.ODDELOVAC3 + Informace.DUCH + Informace.ODDELOVAC3 + Informace.NIC;
        }
        if (clanekHada == 0) {
            if (tank) {
                return clanky.get(clanekHada).getX() + Informace.ODDELOVAC3 + clanky.get(clanekHada).getY() + Informace.ODDELOVAC3 + getBarva(clanekHada).getRGB() + Informace.ODDELOVAC3 + Informace.HLAVA_TANKU + Informace.ODDELOVAC3 + Direction.prevedSmerNaCislo(getExistujiciSmer());
            } else {
                return clanky.get(clanekHada).getX() + Informace.ODDELOVAC3 + clanky.get(clanekHada).getY() + Informace.ODDELOVAC3 + getBarva(clanekHada).getRGB() + Informace.ODDELOVAC3 + Informace.HLAVA + Informace.ODDELOVAC3 + Direction.prevedSmerNaCislo(getExistujiciSmer());
            }
        }
        if (odolnost) {
            return clanky.get(clanekHada).getX() + Informace.ODDELOVAC3 + clanky.get(clanekHada).getY() + Informace.ODDELOVAC3 + getBarva(clanekHada).getRGB() + Informace.ODDELOVAC3 + Informace.ODOLNOST + Informace.ODDELOVAC3 + Informace.NIC;
        }
        return clanky.get(clanekHada).getX() + Informace.ODDELOVAC3 + clanky.get(clanekHada).getY() + Informace.ODDELOVAC3 + getBarva(clanekHada).getRGB() + Informace.ODDELOVAC3 + Informace.CLANEK + Informace.ODDELOVAC3 + Informace.NIC;
    }

    /**
     * Vrátí instrukce pro kreslení, které mohou být poslány klientům.
     * Instrukce pro kreslení se týkají všech článků hada a jeho střel.
     * @param invisible Při true posílá i neviditelné hady.
     * @return Instrukce pro kreslení.
     */
    public String getInstrukceProKresleni(boolean invisible) {
        String instrukce = "";

        // instrukce pro poslání článků hada
        if (!neviditelnost || invisible) {
            for (int i = 0; i < getDelka(); i++) {
                instrukce += Informace.HAD + Informace.ODDELOVAC3;
                instrukce += getClanek(i);
                if (neviditelnost) {
                    instrukce += Informace.ODDELOVAC3 + Informace.NEVIDITELNOST;
                }
                instrukce += Informace.ODDELOVAC2;
            }
        }
        return instrukce;
    }

    /**
     * Vykreslí všechny články hada, jeho střely a rakety.
     * Kreslí podle vlastností hada. Např. duch = menší články.
     * Pokud je velký a had má vlastnost blikající, ocas bliká.
     * @param g
     */
    public void draw(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;

        for (int i = 0; i < clanky.size(); i++) {
            g2d.setPaint(getBarva(i)); // nastavení barvy pro konkrétní článek hada
            if (neviditelnost) {
                if (player.getPacket() == null && player.getAI() == null) { // pokud tento had patří serveru a hráči (ne AI), vykreslí se
                    Clanek.NastavNeviditelneKresleni(g2d);
                } else { // pokud klientovi, nevykreslí se
                    return;
                }
            }
            if (duch) {
                Clanek.paintDuch(g2d, clanky.get(i).getX(), clanky.get(i).getY(), tloustka);
            } else if (odolnost && i != 0) {
                Clanek.paintOdolny(g2d, clanky.get(i).getX(), clanky.get(i).getY(), tloustka);
            } else {
                // vykreslení v případě žádných vlastností ovlivňující vzhled těla
                if (i == 0) {
                    if (tank) {
                        Clanek.paintHlavaTank(g2d, getX(), getY(), tloustka, getExistujiciSmer());
                    } else {
                        Clanek.paintHlava(g2d, getX(), getY(), tloustka, getExistujiciSmer());
                    }
                } else {
                    Clanek.paint(g2d, clanky.get(i).getX(), clanky.get(i).getY(), tloustka);
                }
            }
        }
        // nastavení průhlednosti zpět na normální hodnotu
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1));
    }
}
